﻿using System;
using System.Collections.Generic;
using System.Reflection;
using System.Threading;
using Cysharp.Threading.Tasks;
using Internal.KuiHuaBaoDian;
using KuiHuaBaoDian.Services;

namespace KuiHuaBaoDian {

    /// <summary>
    /// 框架环境
    /// </summary>
    public interface IRuntime {
    }

    /// <summary>
    /// 运行时上下文
    /// </summary>
    public interface IContext {

        bool IsApplicationQuit { get; }

        bool IsApplicationPlaying { get; }

        bool IsApplicationPlaying2 { get; }

        CancellationToken ApplicationQuitCancellationToken { get; }

        bool IsCancellationRequested(CancellationToken cancellationToken);

        UniTask Delay(int millisecondsDelay, CancellationToken cancellationToken = default, bool ignoreTimeScale = false);

        UniTask WaitUntil(Func<bool> predicate, CancellationToken cancellationToken = default);

        UniTask WaitWhile(Func<bool> predicate, CancellationToken cancellationToken = default);
    }

    internal sealed class KHBD_Runtime : IRuntime, IContext, IContextInternal {

        private HashSet<IService> m_Services = new();

        public KHBD_Runtime() {
            ApplicationQuitCancellationToken = m_ApplicationQuitCancellationTokenSource.Token;
        }

        public bool IsCancellationRequested(CancellationToken cancellationToken) {
            return ApplicationQuitCancellationToken.IsCancellationRequested || cancellationToken.IsCancellationRequested;
        }

        public async UniTask Delay(int millisecondsDelay, CancellationToken cancellationToken, bool ignoreTimeScale = false) {
            using var cts = CancellationTokenSource.CreateLinkedTokenSource(ApplicationQuitCancellationToken, cancellationToken);
            cancellationToken = cts.Token;

            if (IsCancellationRequested(cancellationToken)) {
                return;
            }

            await UniTask.Delay(
                millisecondsDelay: millisecondsDelay,
                ignoreTimeScale: ignoreTimeScale,
                delayTiming: PlayerLoopTiming.Update,
                cancellationToken: cancellationToken,
                cancelImmediately: true
            ).SuppressCancellationThrow();
        }

        public async UniTask WaitUntil(Func<bool> predicate, CancellationToken cancellationToken) {
            UnityEngine.Assertions.Assert.IsNotNull(predicate);

            using var cts = CancellationTokenSource.CreateLinkedTokenSource(ApplicationQuitCancellationToken, cancellationToken);
            cancellationToken = cts.Token;

            if (IsCancellationRequested(cancellationToken)) {
                return;
            }

            await UniTask.WaitUntil(
                predicate: predicate,
                timing: PlayerLoopTiming.Update,
                cancellationToken: cancellationToken,
                cancelImmediately: true
            ).SuppressCancellationThrow();
        }

        public async UniTask WaitWhile(Func<bool> predicate, CancellationToken cancellationToken) {
            UnityEngine.Assertions.Assert.IsNotNull(predicate);

            using var cts = CancellationTokenSource.CreateLinkedTokenSource(ApplicationQuitCancellationToken, cancellationToken);
            cancellationToken = cts.Token;

            if (IsCancellationRequested(cancellationToken)) {
                return;
            }

            await UniTask.WaitWhile(
                predicate: predicate,
                PlayerLoopTiming.Update,
                cancellationToken: cancellationToken,
                cancelImmediately: true
            ).SuppressCancellationThrow();
        }

        public bool IsApplicationQuit { get; private set; }

        public bool IsApplicationPlaying2 => IsApplicationPlaying;
        public bool IsApplicationPlaying {
            get {
                if (UnityEngine.Application.isPlaying && !IsApplicationQuit) {
                    return true;
                }
                throw new ApplicationException("应用程序退出");
            }
        }

        private readonly CancellationTokenSource m_ApplicationQuitCancellationTokenSource = new();
        public CancellationToken ApplicationQuitCancellationToken { get; private set; }

        void IContextInternal.AddService(IService service) {
            m_Services.Add(service);
        }

        void IContextInternal.ApplicationQuit() {
            IsApplicationQuit = true;
            m_ApplicationQuitCancellationTokenSource.Cancel();
            foreach (var service in m_Services) {
                service.OnApplicationQuit();
            }
            m_Services = null;
        }

        void IContextInternal.RegisterAssembly(Assembly assembly, bool logEnabled) {
            if (assembly.FullName.StartsWith("Unity")) {
                return;
            }
            var registeredSet = new HashSet<Type>();
            foreach (var type in assembly.DefinedTypes) {
                RegisterType(type, registeredSet, KHBD.TypeMap, logEnabled);
            }
        }

        private static void RegisterType(Type type, HashSet<Type> registeredSet, ITypeMap typeMap, bool logEnabled = true) {
            if (type.IsInterface || type.IsAbstract || type.IsEnum) {
                return;
            }
            var injectAttributes = type.GetCustomAttributes<ImplementAttribute>(true);
            foreach (var attribute in injectAttributes) {
                if (attribute?.InterfaceType != null) {
                    var interfaceType = attribute.InterfaceType;
                    UnityEngine.Assertions.Assert.IsFalse(registeredSet.Contains(interfaceType), $"单个 [程序集({type.AssemblyQualifiedName})] 中发现多个 [目标类型({nameof(ImplementAttribute)}.{nameof(ImplementAttribute.InterfaceType)}={interfaceType.FullName})] 映射 \n当前 [类型({type.FullName})], 已经存在的 [类型({(registeredSet.TryGetValue(interfaceType, out var actualValue) ? actualValue.FullName : null)})]");
                    typeMap.Register(interfaceType, type, logEnabled);
                    registeredSet.Add(interfaceType);
                }
            }
        }
    }
}

namespace Internal.KuiHuaBaoDian {

    public interface IContextInternal {

        void RegisterAssembly(Assembly assembly, bool logEnabled = true);

        void ApplicationQuit();

        void AddService(IService service);
    }
}